﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Dolly
{
	/// <summary>
	/// Cloner allows to create up to 64 copies of thread for parallel reading
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public class Cloner<T> : ICloner<T>
	{
		private IEnumerable<T> _input;
		private IEnumerator<T> _inputEnumerator;
		private Dictionary<string, AutoResetEvent> _iVeReadMyValue;
		private Dictionary<string, AutoResetEvent> _imReadyToRead;
		private WaitHandle[] _ivAlreadyReadArray;
		private Thread _writerThread;
		private T _nextObject;
		private bool _hasCurrent;
		private bool _readingAllowed;
		private bool _collectionIsEmpty;
		private bool _hasNext;

		/// <summary>
		/// Creates instance, that clones enumerable from param
		/// </summary>
		/// <param name="input">some enumerable</param>
		public Cloner(IEnumerable<T> input)
		{
			_input = input;
			_inputEnumerator = _input.GetEnumerator();
			_iVeReadMyValue = new Dictionary<string, AutoResetEvent>();
			_imReadyToRead = new Dictionary<string, AutoResetEvent>();
			_readingAllowed = false;
			_writerThread = new Thread(GetNext);
		}

		private IEnumerable<T> GetClone(string subscriber)
		{
			if (_readingAllowed) throw new AccessViolationException("Subscribers are already reading");
			lock (_iVeReadMyValue)
			{
				_iVeReadMyValue.Add(subscriber, new AutoResetEvent(false));
				_imReadyToRead.Add(subscriber, new AutoResetEvent(false));
				return GetCloneInstance(subscriber);
			}
		}

		/// <summary>
		/// returns cloned copy for enumerable
		/// </summary>
		/// <returns></returns>
		public IEnumerable<T> GetClone()
		{
			return GetClone(Guid.NewGuid().ToString());
		}

		/// <summary>
		/// If you've made enough copies, now you can allow reading from clones
		/// </summary>
		public void AllowReading()
		{
			bool hasFirst = _inputEnumerator.MoveNext();
			_nextObject = _inputEnumerator.Current;
			_collectionIsEmpty = !hasFirst;
			if (_collectionIsEmpty) { _hasCurrent = false; }
			else _hasCurrent = true;
			InitWaiters();
			_writerThread.Start();
			_readingAllowed = true;
		}

		private void GetNext()
		{
			if (!_hasCurrent) return;
			foreach (var ready in _imReadyToRead) ready.Value.Set();
			do
			{
				WaitHandle.WaitAll(_ivAlreadyReadArray);
				_hasNext = _inputEnumerator.MoveNext();
				_nextObject = _inputEnumerator.Current;
				lock (_imReadyToRead)
				{
					if (!_hasNext) _hasCurrent = false;
					foreach (var ready in _imReadyToRead) ready.Value.Set();
				}
			} while (_hasNext);
		}

		private T GetCurrent(string subscriber)
		{
			T toReturn;
			_imReadyToRead[subscriber].WaitOne();
			if (!_hasCurrent) throw new NullReferenceException("RequestedParam");
			toReturn = _nextObject;
			_iVeReadMyValue[subscriber].Set();
			return toReturn;
		}

		private void InitWaiters()
		{
			_ivAlreadyReadArray = _iVeReadMyValue.Select(k => k.Value).OfType<WaitHandle>().ToArray();
		}

		private IEnumerable<T> GetCloneInstance(string key)
		{
			if (!_readingAllowed) throw new AccessViolationException("Reading is not allowed");
			if (_collectionIsEmpty) yield break;
			T res;
			do
			{
				try { res = GetCurrent(key); }
				catch (NullReferenceException)
				{ yield break; }
				yield return res;
			} while (true);
		}
	}
}
